import re

# FIXME: all4() shouldn't take |size| arg -- points now know their board size.

# TODO: rename all4() -- touching_points()?
# TODO: i create these a lot, and they're immutable; could i store them after creation in a static 2d array, and then return the same Point objects next time i'm asked?  (would that help?)
# TODO: override __setattr__ (or __setattribute__?) so it's (mostly) immutable?
# TODO: document it
# TODO: add assertions for str/sgf cases in __init__
# TODO: deal with non-square boards, eventually?

class Point(object):
    """an point on a go board.  immutable.

    create with: Point(col,row), Point(str='A1'), or Point(sgf='bq',size=13)
    (size is required for sgf args with size!=19, because Point -- like everybody else in the world --
    counts from A1=bottom-left, while sgf counts from top-left)

    access with: p.col, p.row (but don't change these!).  0,0 = A1

    print with: str(p) gives the human-readable 'A1', etc.  repr gives <gimme.go.Point A1>.

    can test for equality with ==,!=

    can get points touching this one with all4(size), where size is the size of the board (usually 19).
    """

    __slots__ = ['col', 'row', 'size']

    def __init__(self, col=0, row=0, str="", sgf='', size=19):
        """specify either col/row, or str (not both!).  0,0 = A1.
        typical usage: Point(x,y), or Point(str="B3").
        thirdly, you can use sgf= for sgf-style points, like Point(sgf="dd").
        since sgf indexes from the top-left, and everybody else indexes from the bottom-left,
        sgf= needs to know the size of the board; this is assumed to be 19, unless you also specify size=.
        """
        if str:
            match = re.match("([A-Za-z])(\\d+)", str)
            (letter, number) = match.groups()

            self.col = ord(letter.upper()) - ord('A')
            self.row = int(number) - 1

            if (ord(letter.upper()) >= ord('I')):
                self.col = self.col - 1

        elif sgf:
            if sgf[0].isupper():
                # according to FF[4], upper-case letters go after the
                # lower-case, allowing boards up to 52x52.  (see
                # http://www.red-bean.com/sgf/go.html#types) i.e., the
                # point down-right of 'zz' is 'AA'.
                self.col = ord(sgf[0]) - ord('A') + 26
            else:
                self.col = ord(sgf[0]) - ord('a')
            if sgf[1].isupper():
                self.row = size - 1 - (ord(sgf[1]) - ord('A') + 26)
            else:
                self.row = size - 1 - (ord(sgf[1]) - ord('a'))

        else:
            # (make sure you didn't type Point("B4"), which is a common mistake.)
            assert isinstance(col, int)
            assert isinstance(row, int)
            assert 0 <= col < size
            assert 0 <= row < size

            self.col = col
            self.row = row

        assert 0 < size <= 52 # SGF FF[4] only allows board sizes up to 52!
        self.size = size

    def __hash__(self):
        return self.col**2 + self.row**3 # pick something...

    def __str__(self):
        if self.size <= 19:
            extra = 0
            if (self.col >= (ord('I') - ord('A'))):
                extra = 1
            letter = chr(ord('A') + self.col + extra)
            number = self.row + 1
            return "%c%d" % (letter, number)
        else:
            # if it's beyond 19x19, we'll just use ints
            return "(%d,%d)" % (self.col, self.row)
    def __repr__(self):
        return "<gimme.go.Point %s>" % self.__str__()

    def as_sgf(self):
        """
        it's 2 chars, like aa.
        - a=1, b=2, ..., z=26, A=27, B=28, ..., Z=52
        - aa is top-left, not bottom-left (!)
        """

        if self.col < 26:
            col_char = chr(ord('a') + self.col)
        else:
            col_char = chr(ord('A') + self.col - 26)

        row = self.size - self.row - 1 # flip row for SGF
        if row < 26:
            row_char = chr(ord('a') + row)
        else:
            row_char = chr(ord('A') + row - 26)

        return "%c%c" % (col_char, row_char)


    def __eq__(self, pt2):
        return isinstance(pt2, Point) and (self.col,self.row) == (pt2.col,pt2.row)
    def __ne__(self, pt2):
        return not self==pt2

    def all4(self, size):
        """return points touching this one (N/S/E/W).  only returns
        points on the board (as specified by |size|) -- so this can
        return a list of from 2 to 4 points."""
        dirs = [ (0,1), (1,0), (0,-1), (-1,0) ]
        return [ Point(self.col+dx, self.row+dy) for (dx,dy) in dirs
                 if 0<=self.col+dx<size and 0<=self.row+dy<size ]
